package com.sunshine.client.factory.support;

import com.sunshine.client.annotation.ApiServer;
import com.sunshine.client.bean.MethodInfo;
import com.sunshine.client.bean.ServiceInfo;
import com.sunshine.client.factory.ProxyCreator;
import com.sunshine.client.factory.RestHandler;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author Mokairui
 * @description frame-module
 * @since 2022/5/8
 */
@Slf4j
@Service
public class JDKProxyCreator implements ProxyCreator {
//    private Logger log = LoggerFactory.getLogger(JDKProxyCreator.class);
    
    @Override
    public Object createProxy(Class<?> type) {
        log.debug("createProxy: " + type);
        
        // 根据接口获取 API 服务器信息
        ServiceInfo serviceInfo = extractServiceInfo(type);
        log.debug("serviceInfo: " + serviceInfo);

        RestHandler restHandler = new WebClientRestHandler();
        restHandler.init(serviceInfo);
        
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{type}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 根据方法和参数得到调用信息
                MethodInfo methodInfo = extractMethodInfo(method, args);
                log.debug("methodInfo: " + methodInfo);
                
                // 调用rest
                return restHandler.invokeRest(methodInfo);
            }
        });
    }

    private MethodInfo extractMethodInfo(Method method, Object[] args) {
        MethodInfo methodInfo = new MethodInfo();
        Annotation[] methodAnnotations = method.getAnnotations();
        
        extractUrlAndMethod(methodInfo, methodAnnotations);

        extractRequestParamAndBody(method, args, methodInfo);
        
        // 提取返回对象信息
        extractReturnInfo(method, methodInfo);

        return methodInfo;
    }

    private void extractReturnInfo(Method method, MethodInfo methodInfo) {
        // 判断返回 flux 还是 mono
        boolean isFluxClass = method.getReturnType().isAssignableFrom(Flux.class);
        methodInfo.setReturnFlux(isFluxClass);
        
        // 得到返回对象的实际类型(就是泛型中的类型)
        Class<?> elementType = extractElementType(method.getGenericReturnType());
        methodInfo.setReturnElementType(elementType);
    }

    private Class<?> extractElementType(Type genericReturnType) {
        Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
        return (Class<?>) actualTypeArguments[0];
    }

    private void extractRequestParamAndBody(Method method, Object[] args, MethodInfo methodInfo) {
        // 得到调用的参数和 body
        Parameter[] parameters = method.getParameters();

        // 参数和值对应的 map
        Map<String, Object> params = new LinkedHashMap<>();
        methodInfo.setParams(params);

        for (int i = 0; i < parameters.length; i++) {
            // 是否带有 @PathVariable
            PathVariable annoPath = parameters[i].getAnnotation(PathVariable.class);
            if (annoPath != null) {
                params.put(annoPath.value(), args[i]); // 含有 @PathVariable 注解, 根据注解中的标识储存值
            }
            
            // 是否携带 @RequestBody
            RequestBody annoBody = parameters[i].getAnnotation(RequestBody.class);
            if (annoBody != null) {
                methodInfo.setBody((Mono<?>) args[i]);
                methodInfo.setBodyElementType(extractElementType(parameters[i].getParameterizedType()));
            }
        }
    }

    private void extractUrlAndMethod(MethodInfo methodInfo, Annotation[] methodAnnotations) {
        for (Annotation methodAnnotation : methodAnnotations) {
            if (methodAnnotation instanceof GetMapping) {
                GetMapping getMapping = (GetMapping) methodAnnotation;
                
                methodInfo.setUrl(getMapping.value()[0]);
                methodInfo.setMethod(HttpMethod.GET);
            } else if (methodAnnotation instanceof PostMapping) {
                PostMapping postMapping = (PostMapping) methodAnnotation;

                methodInfo.setUrl(postMapping.value()[0]);
                methodInfo.setMethod(HttpMethod.POST);
            } else if (methodAnnotation instanceof DeleteMapping) {
                DeleteMapping getMapping = (DeleteMapping) methodAnnotation;

                methodInfo.setUrl(getMapping.value()[0]);
                methodInfo.setMethod(HttpMethod.DELETE);
            }
        }
    }

    private ServiceInfo extractServiceInfo(Class<?> type) {
        ServiceInfo serviceInfo = new ServiceInfo();
        ApiServer anno = type.getAnnotation(ApiServer.class);
        serviceInfo.setUrl(anno.value());
        return serviceInfo;
    }
}
